/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package net.thick4.context;

import net.thick4.convention.Convention;

import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.SmartClassLoader;
import org.springframework.util.Assert;

/**
 * Hotdeploy 可能なクラスを読み込むためのClassLoaderです。
 * Hotdeploy 対象のパッケージをincludePackageメソッドで指定します。 
 * @see org.springframework.core.OverridingClassLoader
 * @see org.springframework.core.SmartClassLoader
 */
public class ReloadableClassLoader extends OverridingClassLoader implements SmartClassLoader {
		
    /**
     * Hotdeploy対象外パッケージ
     */
    private static final String[] DEFAULT_EXCLUDED_PACKAGES =
        new String[] {
    		"org.springframework.",
    		"net.thick4.convention.",
    	};
	
    /**
     * 規約です。
     */
    private Convention convention;
    
	/**
	 * コンストラクタです。
	 * @param parent 親ClassLoaader
	 * @param convention 規約
	 */
	public ReloadableClassLoader(ClassLoader parent, Convention convention) {
		super(parent);
		this.convention = convention;
		excludePackages(DEFAULT_EXCLUDED_PACKAGES);
	}
	
	/**
	 * 読み込み対象外パッケージ名を指定します。
	 * @param packageNames 読み込み対象パッケージ名
	 */
	public void excludePackages(String[] packageNames) {
		Assert.notNull(packageNames, "Package names must not be null");
		for (int i=0; i<packageNames.length; ++i) {
			if (packageNames[i].endsWith(".")) {
				this.excludePackage(packageNames[i]);
			} else {
				this.excludePackage(packageNames[i] + ".");
			}
		}
	}
	
	/**
	 * Spring AOP (CGILIG実装）を利用時に、指定したクラス名のクラスを
	 * キャッシュさせるかどうかを判定するメソッドです。
	 * このClassLoaderの読み込み対象クラスはキャッシュさせないようにfalseを返します。
	 * @see org.springframework.core.SmartClassLoader#isClassReloadable(Class)
	 * @see org.springframework.core.DecoratingClassLoader#isExclude(String)
	 * @see #isInclude(String)
	 */
	public boolean isClassReloadable(Class klass) {
		return klass.getClassLoader() instanceof ReloadableClassLoader;
	}
	
	/**
	 * ClassLoaderを破棄する時に呼び出すメソッドです。
	 * Spring内部で保持されているClassLoaderに関連するキャッシュを破棄します。
	 */
	public void dispose() {
		CachedIntrospectionResults.clearClassLoader(this);
	}
	
	/**
	 * 指定したクラス名が読み込み対象かどうかを判定します。
	 * @param className チェック対象クラス名
	 * @return 指定したクラス名が読み込み対象の場合にtrueを返します。
	 */
	protected boolean isIncluded(String className) {
		return convention.includeBasePackage(className);
	}
	
	/**
	 * 指定したクラス名が読み込み対象クラスかどうかを判定して返します。
	 * isIncludedがtrue、かつ、isExcludedがfalseを返す場合にtrueを返します。
	 * @param className チェック対象クラス名
	 * @return 指定したクラス名が読み込み対象の場合にtrueを返します。
	 * @see org.springframework.core.OverridingClassLoader#isEligibleForOverriding(String)
	 * @see #isIncluded(String)
	 * @see #isExcluded(String)
	 */
	protected boolean isEligibleForOverriding(String className) {
		return (isIncluded(className) && !isExcluded(className))
			|| ReloadableUtil.REBUILDER_CLASS_NAME.equals(className);
	}
	
}
